package sm.div;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.orm.hibernate3.HibernateTemplate;

import sm.codec.type.NullValue;
import sm.codec.type.Struct;
import sm.dam.api.DBDictService;
import sm.dam.api.DataService;
import sm.dbdict.DBDictUtil;
import sm.dbdict.def.DBDict;
import sm.dbdict.def.Field;
import sm.dbdict.def.Key;
import sm.def.Type;
import sm.div.api.DataTypeValidator;
import sm.hibernate.DAMUtil;
import sm.util.SMMap;
import sm.util.Util;

import com.google.gson.Gson;

public class DataTypeValidatorImpl implements DataTypeValidator {

	private static Logger logger = LoggerFactory
			.getLogger(DataTypeValidator.class);

	private DataService dataService;

	private HibernateTemplate template;

	public DataTypeValidatorImpl(ApplicationContext context) {
		template = (HibernateTemplate) context.getBean("template");
		dataService = (DataService) context.getBean("dataService");
	}

	@Override
	public void validateDataType() {
		logger.info("Begin validate data type");
		List<Object> list = template.find("select name from dbdict");
		for (Object obj : list) {
			String fileName = (String) obj;
			validateDataType(fileName);
		}
		logger.info("End validate data type");
	}

	private List<Object[]> createCheckValues(List<Field> fields) {
		List<Object[]> checkValues = new ArrayList<Object[]>();
		for (Field field : fields) {
			Field arrayParent = DBDictUtil.getArrayParent(field);
			String arrayPath = null;
			String fieldPath = null;
			if (arrayParent != null) {
				arrayPath = DBDictUtil.getFieldPath(arrayParent);
				fieldPath = DBDictUtil.getArrayFieldPath(field, arrayParent);
			} else {
				fieldPath = DBDictUtil.getFieldPath(field);
			}

			if (arrayPath != null) {
				arrayPath = Util.radFieldPathToJava(arrayPath);
			}
			fieldPath = Util.radFieldPathToJava(fieldPath);
			int type = field.getType();
			checkValues.add(new Object[] { arrayPath, fieldPath, type });
		}
		return checkValues;
	}

	private boolean isAutoTransformAble(int curType, int expectedType,
			Object value) {
		boolean canTransfer = false;
		if (expectedType == Type.RECORD_TYPE) {
			// record type can be any type
			canTransfer = true;
		} else if (expectedType == Type.CHARACTER_TYPE
				&& Type.isPrimitiveType(curType)) {
			// any primitive type can be transferred to character by toString
			canTransfer = true;
		} else if (expectedType == Type.EXPRESSION_TYPE) {
			// expression type can be any type
			canTransfer = true;
		} else {
			if (curType == Type.OPERATOR_TYPE
					|| curType == Type.GLOBALVARIABLE_TYPE
					|| curType == Type.LOCALVARIABLE_TYPE
					|| curType == Type.MEMBER_TYPE
					|| curType == Type.OFFSET_TYPE
					|| curType == Type.CHARACTER_TYPE) {
				// expression and variables and literal expressions and offset
				// can be any other type
				canTransfer = true;
			} else if (curType == Type.LOGICAL_TYPE) {
				if (expectedType == Type.NUMBER_TYPE) {
					// logical can be transferred to number
					canTransfer = true;
				}
			} else if (curType == Type.ARRAY_TYPE) {
				List<Object> list = null;
				if (value instanceof List) {
					list = (List<Object>) value;
				} else if (value instanceof Struct) {
					Struct struct = (Struct) value;
					list = struct.getValue();
				}
				if (list.size() == 1) {
					// array of 1 element can be transferred to any type;
					Object fieldValue = list.get(0);
					canTransfer = isAutoTransformAble(
							DAMUtil.getTypeFromValue(fieldValue), expectedType,
							fieldValue);
				}
			}
		}
		return canTransfer;
	}

	private boolean checkType(String fieldPath, Object value, int expectedType,
			String fileName, Object[] keyValue, SMMap dataMap) {
		boolean hasConflict = false;
		if (value != null && !(value instanceof NullValue)) {
			int realType = DAMUtil.getTypeFromValue(value);
			if (expectedType != realType) {
				hasConflict = true;
				String keyMsg = "Data type missmatch for dbdict:" + fileName
						+ ",field:" + fieldPath + ",key:"
						+ Util.formatKey(keyValue);
				Gson gson = new Gson();
				String valueMsg = "value:" + gson.toJson(value) + ", type is "
						+ Type.getType(realType).getName()
						+ " but expected to be "
						+ Type.getType(expectedType).getName();
				if (isAutoTransformAble(realType, expectedType, value)) {
					logger.debug(keyMsg);
					logger.debug(valueMsg);
				} else {
					logger.info(keyMsg);
					logger.info(valueMsg);
				}
			}
		}
		return hasConflict;
	}

	@Override
	public void validateDataType(String fileName) {
		if (logger.isDebugEnabled()) {
			logger.debug("Validate data type for " + fileName);
		}
		SMMap dbdictMap = (SMMap) template.get(DBDictService.DBDICT, fileName);
		if (dbdictMap != null) {
			DBDict dbdict = (DBDict) dbdictMap.get(DBDictService.DESCRIPTOR);
			Key identifer = DBDictUtil.getIdentifier(dbdict);
			List<Field> fields = dbdict.getField();
			List<Field> identiferFields = DBDictUtil.getKeyFields(identifer,
					fields);
			String[] keyPaths = DBDictUtil.getKeyPaths(identiferFields);
			keyPaths = Util.radFieldPathsToJava(keyPaths);

			Field descriptor = fields.get(0);
			List<Field> realFields = DBDictUtil.getRealSubFields(descriptor,
					fields);
			List<Object[]> checkValues = createCheckValues(realFields);

			String entityName = Util.radEntityNameToJava(fileName);

			String[] joinedArrayPaths = DAMUtil.getJoinedArrayPaths(dbdict);
			int firstResult = 0;
			int batchSize = 500;
			List<SMMap> datas = dataService.getNextBatchDatas(entityName,
					firstResult, batchSize, joinedArrayPaths);
			while (datas != null && datas.size() > 0) {
				for (SMMap data : datas) {
					Object[] keyValue = DAMUtil.getValues(data, keyPaths);
					for (Object[] checkValue : checkValues) {
						String arrayPath = (String) checkValue[0];
						String arrayFieldPath = (String) checkValue[1];
						int expectedType = (Integer) checkValue[2];
						if (arrayPath != null) {
							Object arrayObj = data.get(arrayPath);
							if (arrayObj != null) {

								if (arrayObj instanceof List) {
									List<Object> arrayFields = (List<Object>) arrayObj;
									for (int i = 0; i < arrayFields.size(); i++) {
										Object arrayField = arrayFields.get(i);
										if (arrayFieldPath != null
												&& arrayFieldPath.length() > 0) {
											if (arrayField instanceof SMMap) {
												SMMap arrayFieldMap = (SMMap) arrayField;
												Object fieldValue = arrayFieldMap
														.get(arrayFieldPath);
												checkType(
														arrayPath
																+ "["
																+ i
																+ "]."
																+ arrayFieldPath,
														fieldValue,
														expectedType, fileName,
														keyValue, data);
											} else {
												checkType(arrayPath + "[" + i
														+ "]", arrayField,
														Type.STRUCTURE_TYPE,
														fileName, keyValue,
														data);
											}
										} else {
											checkType(
													arrayPath + "[" + i + "]",
													arrayField, expectedType,
													fileName, keyValue, data);
										}
									}
								} else {
									checkType(arrayPath, arrayObj,
											expectedType, fileName, keyValue,
											data);
								}
							}
						} else {
							Object fieldValue = data.get(arrayFieldPath);
							checkType(arrayFieldPath, fieldValue, expectedType,
									fileName, keyValue, data);
						}

					}
				}

				// fetch next batch
				firstResult += 500;
				datas = dataService.getNextBatchDatas(entityName, firstResult,
						batchSize, joinedArrayPaths);
			}
		}
	}
}
